/*
 * Copyright (c) 2007, Nathan Sturtevant
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the University of Alberta nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY NATHAN STURTEVANT ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL <copyright holder> BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package com.googlecode.pathmachine.mm;

import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;

/**
 *
 * Map
 *
 * This code is simplified from a more complex map structure.
 * This map class 
 * 
 * @author Nathan Sturtevant
 * @author <a href="mailto:eriq.adams@gmail.com">Eriq Muhammad Adams J.</a>
 */
public class Map {

    public static final float ONE = 1.0f;
    public static final float TWO = 2.0f;
    public static final float ROOT_TWO = 1.414213562f;//or 1.5f if desired
    public static final float ONE_OVER_ROOT_TWO = 0.707106781f;
    private String mapName;
    private int width;
    private int height;
    private String type;
    private List<Integer> land;
    private float tileSize;
    private float halfRealWidth;
    private float halfRealHeight;

    /** 
     * Map()
     *
     * Create a new map by loading it from a file with given tile size.
     *
     * Creates a new map and initializes it with the file passed to it.
     *
     * @param filename The path of a file containing a map.
     * @return none
     */
    public Map(InputStream is, float tileSize) throws IOException {
        load(is);
        this.tileSize = tileSize;
        this.halfRealWidth = (width * tileSize) / 2f;
        this.halfRealHeight = (height * tileSize) / 2f;

    }

    /** 
     * Map()
     *
     * Create a new map by loading it from a file with default tile size.
     *
     * Creates a new map and initializes it with the file passed to it.
     *
     * @param filename The path of a file containing a map.
     * @return none
     */
    public Map(InputStream is) throws IOException {
        this(is, 0.5f);
    }

    /** 
     * load()
     *
     * Resets the current map by loading the file passed in.
     *
     * Resets the current map by loading the file passed in.
     *
     * @param is The input stream to load
     * @return none
     */
    private void load(InputStream is) throws IOException {
        DataInputStream dis = new DataInputStream(is);
        BufferedReader reader = new BufferedReader(new InputStreamReader(dis));
        String mapLine;
        boolean loadOctile = false;
        int y = 0;
        land = new ArrayList<Integer>();
        while ((mapLine = reader.readLine()) != null) {
            mapLine = mapLine.replaceAll(" ", "");
            if (mapLine.startsWith("type")) {
                this.type = mapLine.replaceAll("type", "");
            } else if (mapLine.startsWith("height")) {
                this.height = Integer.parseInt(mapLine.replaceAll("height",
                        ""));
            } else if (mapLine.startsWith("width")) {
                this.width = Integer.parseInt(mapLine.replaceAll("width", ""));
            } else if (loadOctile) {
                loadOctile(mapLine, y);
                y++;
            } else if (mapLine.startsWith("map")) {
                loadOctile = true;
            }
        }
    }

    /**
     * loadOctile()
     *
     * Helper function for loading an octile map
     * 
     * After the header has been ready, 
     * following header lines there is a "@" or an "O" for blocked land
     * and any other character for unblocked land.
     *
     * @param f and open file pointer
     * @param high The height of the map being loaded (number of lines)
     * @param wide The width of the map being loaded (column width)
     * @return none
     */
    private void loadOctile(String mapLine, int y) {
        for (int x = 0; x < width; x++) {
            char c = mapLine.charAt(x);
            if ('@' == c || 'T' == c) {
                land.add(getIndex(x, y), 0);
            } else {
                land.add(getIndex(x, y), 1);
            }
        }
    }

    /**
     * getMapName()
     *
     * brief Returns the file name of the map, if available
     *
     * @return A pointer to a string containing the name of the map.
     */
    public String getMapName() {

        return mapName;
    }

    /**
     * @return the width of map
     */
    public int getMapWidth() {
        return width;
    }

    /**
     * @return the type of map
     */
    public String getType() {
        return type;
    }

    /**
     * @return the height of map
     */
    public int getMapHeight() {
        return height;
    }

    /**
     * getTerrainType()
     *
     * Get the terrain at a particular coordinate.
     *
     * @param x The x-coordinate to query
     * @param y The y-coordinate to query
     * @return The terrain at that coordinate
     */
    public TTerrain getTerrainType(int x, int y) {
        int index = getIndex(x, y);
        if (index == -1) {
            return TTerrain.kOutOfBounds;
        }
        if (land.get(index) == 0) {
            return TTerrain.kOutOfBounds;
        }
        return TTerrain.kGround;
    }

    /**
     * setTerrainType()
     *
     * Set the terrain at a particular coordinate.
     *
     * @param x The x-coordinate to set
     * @param y The y-coordinate to set
     * @param terrain The terrain for that coordinate
     * @return none
     */
    public void setTerrainType(int x, int y, TTerrain terrain) {
        int index = getIndex(x, y);
        if (index == -1) {
            return;
        }
        if (terrain == TTerrain.kOutOfBounds) {
            land.add(index, 0);
        } else {
            land.add(index, 1);
        }
    }
//
//    /**
//     * setTerrainType()
//     *
//     * Set all the terrain between two points to be the same
//     *
//     * @param x1 The first x-coordinate to set
//     * @param y1 The first y-coordinate to set
//     * @param x2 The second x-coordinate to set
//     * @param y2 The second y-coordinate to set
//     * @param terrain The terrain for the line between the coordinates
//     * @return none
//     */
//    public void setTerrainType(int x1, int y1, int x2, int y2, TTerrain t) {
//        double xdiff = (int) x1 - (int) x2;
//        double ydiff = (int) y1 - (int) y2;
//        double dist = Math.sqrt(xdiff * xdiff + ydiff * ydiff);
//        setTerrainType(x1, y1, t);
//        for (double c = 0; c < dist; c += 0.5) {
//            double ratio = c / dist;
//            double newx = (double) x1 - xdiff * ratio;
//            double newy = (double) y1 - ydiff * ratio;
//            setTerrainType((int) newx, (int) newy, t);
//        }
//        setTerrainType(x2, y2, t);
//    }
//
//    /**
//     * setTerrainType()
//     *
//     * Set the terrain at a radius from a point to be the same
//     *
//     * @param x The center x-coordinate
//     * @param y The center y-coordinate
//     * @param radius The radius of the circle
//     * @param terrain The terrain for the circle
//     * @return none
//     */
//    public void setTerrainType(int x, int y, int radius, TTerrain t) {
//        for (int x2 = -radius; x2 <= radius; x2++) {
//            for (int y2 = -radius; y2 <= radius; y2++) {
//                if ((x + x2 >= 0) && (y + y2 >= 0)) {
//                    if ((x2 * x2 + y2 * y2) < radius * radius) {
//                        setTerrainType(x + x2, y + y2, t);
//                    }
//                }
//            }
//        }
//    }

    /**
     * getPointFromCoordinate()
     *
     * Given an openGL coordinate, return the relevant map coordinate.
     *
     * @param loc A 3-d point in openGL space
     * @return 2D Point on the map
     */
    public Point2D getPointFromCoordinate(Point3D loc) {
        float x_loc = loc.x;
        float y_loc = loc.z;
        Point2D p = new Point2D();
        p.x = Math.round(((x_loc + halfRealWidth) / tileSize));
        p.y = Math.round(((y_loc + halfRealHeight) / tileSize));

        return p;
    }

    /** 
     * Given an x/y coordinate, compute the index in the land vector 
     */
    public int getIndex(int x, int y) {
        if (x >= width) {
            return -1;
        }
        if (y >= height) {
            return -1;
        }
        if (x < 0) {
            return -1;
        }

        if (y < 0) {
            return -1;
        }
        return y * width + x;
    }

    /** 
     * Given a location in the land vector, get the x/y coordinate 
     */
    public Point2D getCoordinate(int loc) {
        Point2D p = new Point2D();
        p.y = loc / width;
        p.x = loc % width;
        return p;
    }

    /**
     * getOpenGLCoord()
     *
     * get the openGL coordinates of a given tile.
     *
     * Given a tile in (x, y) coordinates, it returns the OpenGL space coordinates of
     * that tile along with the radius of the tile square. The map is drawn in the
     * x<->y plane, with the negative z plane towards the camera.
     *
     * @param x_loc The x coordinate in the map
     * @param y_loc The y coordinate in the map
     * @return point 3d (x, y, z)
     */
    public Point3D getOpenGLCoord(int x_loc, int y_loc) {
        Point3D loc = new Point3D();
        loc.x = ((x_loc) * tileSize) - halfRealWidth;
        loc.z = ((y_loc) * tileSize) - halfRealHeight;
        return loc;
    }

    /**
     * 
     * @return the tileSize of map
     */
    public float getTileSize() {
        return tileSize;
    }

    /**
     * @return the land
     */
    public List<Integer> getLand() {
        return land;
    }

    /**
     * 
     * @param index
     * @return 
     */
    public int getLand(int index) {
        return land.get(index);
    }
}
